/**
 * 版权所有(C)，上海海鼎信息工程股份有限公司，2007，所有权利保留。
 * 
 * 项目名：	rumba
 * 文件名：	MonthlyConstraint.java
 * 模块说明：	
 * 修改历史：
 * Nov 10, 2007 - lxm - 创建。
 */
package com.hd123.rumba.sysclock;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.Map;

import net.sf.hibernate.HibernateException;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

import com.hd123.rumba.exception.BusinessException;
import com.hd123.rumba.exception.NoneException;
import com.hd123.rumba.mscb.constraint.IConstraint;
import com.hd123.rumba.runtime.FormatSettings;
import com.hd123.rumba.runtime.backprocess.MonthlySchedule;
import com.hd123.rumba.runtime.backprocess.MonthlySchedule.Style;
import com.hd123.rumba.util.DateUtil;
import com.hd123.rumba.util.XMLUtil;

/**
 * 系统时钟每月重复模式消息订阅约束条件。
 * <p>
 * 
 * 调度公式1：从{@link #startDate}到{@link #finishDate}期间，每间隔{@link #every}月的第{@link #numberOfDay}天的指定时间{@link #time}被触发。<br>
 * 调度公式2：从startDate到finishDate期间，每间隔every月的第{@link #numberOfDOW}个{@link #dayOfWeek}的指定时间{@link #time}被触发。
 * <p>
 * 
 * 关于周期的计算，把startDate所在月作为第一月，将其后的时间按照every指定的月数分割为若干段，每个段的第一月中指定日期的指定时间被触发。
 * <p>
 * 
 * 通过{@link #getContext()}序列化格式存在两种情况，当采用调度公式1时：
 * <p style="border: 1px dashed blue;background-color: gainsboro;">
 * <code>
 &lt;?xml version="1.0" encoding="UTF-8"?&gt;<br>
 &lt;monthlyConstraint<br>
 &nbsp;&nbsp;startDate="2008-01-01 00:00:00"<br>
 &nbsp;&nbsp;finishDate="2008-12-31 00:00:00"<br>
 &nbsp;&nbsp;every="2"<br>
 &nbsp;&nbsp;style="byDay"<br>
 &nbsp;&nbsp;numberOfDay="-2"<br>
 &nbsp;&nbsp;time="1970-01-01 17:30:00"/&gt;<br>
 </code>
 * </p>
 * 当采用调度公式2时：
 * <p style="border: 1px dashed blue;background-color: gainsboro;">
 * <code>
 &lt;?xml version="1.0" encoding="UTF-8"?&gt;<br>
 &lt;monthlyConstraint<br>
 &nbsp;&nbsp;startDate="2008-01-01 00:00:00"<br>
 &nbsp;&nbsp;finishDate="2008-12-31 00:00:00"<br>
 &nbsp;&nbsp;every="2"<br>
 &nbsp;&nbsp;style="byWeek"<br>
 &nbsp;&nbsp;numberOfDOW="2"<br>
 &nbsp;&nbsp;dayOfWeek="1"<br>
 &nbsp;&nbsp;time="1970-01-01 17:30:00"/&gt;<br>
 </code>
 * </p>
 * 说明：属性<code>dayOfWeek</code>取值“1”表示星期日，“2”表示星期一，依次类推。
 * <p>
 * 
 * @author lxm
 * 
 */
public class MonthlyConstraint implements IConstraint, IForTaskSchedule {

  private Date startDate = new Date();
  private Date finishDate = null;
  private Integer every = new Integer(1);
  private MonthlySchedule.Style style = MonthlySchedule.Style.BYDAY;
  private Integer numberOfDay = new Integer(1);
  private Integer numberOfDOW = new Integer(1);
  private Integer dayOfWeek = null;
  private Date time = null;
  private Map<String, Object> dict = null;

  public String getContext() throws BusinessException {
    Document doc = XMLUtil.newDocument();
    Element eRoot = doc.createElement("monthlyConstraint");
    XMLUtil.writeDate(eRoot, "startDate", startDate);
    XMLUtil.writeDate(eRoot, "finishDate", finishDate);
    XMLUtil.writeInteger(eRoot, "every", every);
    XMLUtil.writeString(eRoot, "style", style.getValue());
    if (MonthlySchedule.Style.BYDAY.equals(style)) {
      XMLUtil.writeInteger(eRoot, "numberOfDay", numberOfDay);
    } else {
      XMLUtil.writeInteger(eRoot, "numberOfDOW", numberOfDOW);
      XMLUtil.writeInteger(eRoot, "dayOfWeek", dayOfWeek);
    }
    XMLUtil.writeDate(eRoot, "time", time);
    doc.appendChild(eRoot);
    return XMLUtil.transform(doc);
  }

  public void setContext(String context) throws BusinessException {
    Document doc = XMLUtil.parse(context);
    NodeList nlRoot = doc.getElementsByTagName("monthlyConstraint");
    try {
      if (nlRoot.getLength() == 0 || !(nlRoot.item(0) instanceof Element))
        throw new NoneException();
      Element eRoot = (Element) nlRoot.item(0);
      this.startDate = XMLUtil.readDate(eRoot, "startDate");
      this.finishDate = XMLUtil.readDate(eRoot, "finishDate");
      this.every = XMLUtil.readInteger(eRoot, "every");
      this.style = MonthlySchedule.Style.getStyle(XMLUtil.readString(eRoot, "style"));
      if (MonthlySchedule.Style.BYDAY.equals(style)) {
        this.numberOfDay = XMLUtil.readInteger(eRoot, "numberOfDay");
      } else {
        this.numberOfDOW = XMLUtil.readInteger(eRoot, "numberOfDOW");
        this.dayOfWeek = XMLUtil.readInteger(eRoot, "dayOfWeek");
      }
      this.time = XMLUtil.readDate(eRoot, "time");
    } catch (NoneException e) {
      throw new BusinessException("无法识别的约束条件上下文(ConstraintContext)。");
    } catch (ParseException e) {
      throw new BusinessException("无法识别的约束条件上下文(ConstraintContext)。", e);
    }
  }

  public Map<String, Object> getDict() throws BusinessException {
    return dict;
  }

  public void setDict(Map<String, Object> dict) throws BusinessException {
    this.dict = dict;
  }

  public boolean isValid() throws BusinessException, HibernateException {
    assert this.startDate != null && this.every != null && this.time != null;
    assert this.dict != null;
    MonthlySchedule schedule = new MonthlySchedule();
    schedule.setStartDate(startDate);
    schedule.setFinishDate(finishDate);
    schedule.setEvery(every.intValue());
    schedule.setStyle(style);
    if (MonthlySchedule.Style.BYDAY.equals(style)) {
      schedule.setNumberOfDay(numberOfDay.intValue());
    } else {
      schedule.setNumberOfDOW(numberOfDOW.intValue());
      schedule.setDayOfWeek(dayOfWeek.intValue());
    }
    schedule.setTime(time);
    Date notifyTime = (Date) this.dict.get(CSysclock.DICT_NOTIFYTIME);
    assert notifyTime != null;
    return schedule.isValid(notifyTime);
  }

  private static final String[] CAPTION_DAYOFWEEK = {
      "星期日", "星期一", "星期二", "星期三", "星期四", "星期五", "星期六" };

  public String getDescription() throws BusinessException {
    StringBuffer sb = new StringBuffer();
    sb.append("从");
    sb.append(FormatSettings.formatDatetime(FormatSettings.SDFyMd, this.startDate));
    sb.append("开始");
    if (this.finishDate != null) {
      sb.append("到");
      sb.append(FormatSettings.formatDatetime(FormatSettings.SDFyMd, this.finishDate));
      sb.append("期间");
    }
    if (this.every.intValue() == 1)
      sb.append("每个月");
    else {
      sb.append("每");
      sb.append(this.every);
      sb.append("个月");
    }
    sb.append("的");
    if (Style.BYDAY.equals(this.style)) {
      if (this.numberOfDay.intValue() < 0)
        sb.append("倒数");
      sb.append("第");
      sb.append(Math.abs(this.numberOfDay.intValue()));
      sb.append("天的");
    } else {
      if (this.numberOfDOW.intValue() < 0)
        sb.append("倒数");
      sb.append("第");
      sb.append(Math.abs(this.numberOfDOW.intValue()));
      sb.append("个");
      sb.append(CAPTION_DAYOFWEEK[this.dayOfWeek.intValue()]);
      sb.append("的");
    }
    sb.append(FormatSettings.formatDatetime(new SimpleDateFormat("HH:mm"), this.time));
    sb.append("。");

    return sb.toString();
  }

  public void clear() throws BusinessException {
    this.dict = null;
    this.startDate = DateUtil.truncate(new Date(), Calendar.DATE);
    this.finishDate = null;
    this.every = new Integer(1);
    this.style = MonthlySchedule.Style.BYDAY;
    this.numberOfDay = new Integer(1);
    this.numberOfDOW = new Integer(1);
    this.dayOfWeek = null;
    this.time = null;
  }

  public Date getNextTime(Date baseTime) throws BusinessException {
    MonthlySchedule schedule = new MonthlySchedule();
    schedule.setStartDate(startDate);
    schedule.setFinishDate(finishDate);
    schedule.setEvery(every.intValue());
    schedule.setStyle(style);
    if (MonthlySchedule.Style.BYDAY.equals(style)) {
      schedule.setNumberOfDay(numberOfDay.intValue());
    } else {
      schedule.setNumberOfDOW(numberOfDOW.intValue());
      schedule.setDayOfWeek(dayOfWeek.intValue());
    }
    schedule.setTime(time);
    return schedule.getNextTime(baseTime);
  }

  /**
   * 属性：指定周的第几天。可选值为：{@link Calendar#SUNDAY}, ..., {@link Calendar#SATURDAY}<br>
   * 仅当style==Style.BYWEEK时有效。
   */
  public Integer getDayOfWeek() {
    return dayOfWeek;
  }

  /**
   * 属性：指定周的第几天。可选值为：{@link Calendar#SUNDAY}, ..., {@link Calendar#SATURDAY}<br>
   * 仅当style==Style.BYWEEK时有效。
   */
  public void setDayOfWeek(Integer dayOfWeek) throws BusinessException {
    assert (dayOfWeek.intValue() >= Calendar.SUNDAY && dayOfWeek.intValue() <= Calendar.SATURDAY);
    if (dayOfWeek.intValue() < Calendar.SUNDAY || dayOfWeek.intValue() > Calendar.SATURDAY)
      throw new BusinessException("未知一周中的日子\"" + dayOfWeek + "\"。");
    this.dayOfWeek = dayOfWeek;
  }

  /** 间隔天数。默认为1，&gt;0。 */
  public Integer getEvery() {
    return every;
  }

  /** 间隔天数。默认为1，&gt;0。 */
  public void setEvery(Integer every) throws BusinessException {
    if (every.intValue() <= 0)
      throw new BusinessException("属性every必须为大于等于1的整数。");
    this.every = every;
  }

  /** 结束日期。包括这一天。允许null，表示不指定结束日期。 */
  public Date getFinishDate() {
    return finishDate;
  }

  /** 结束日期。包括这一天。允许null，表示不指定结束日期。 */
  public void setFinishDate(Date finishDate) {
    this.finishDate = DateUtil.truncate(finishDate, Calendar.DATE);
  }

  /**
   * 属性：指定第几天。不允许0；允许&lt;0，表示一个月的最后第几天。<br>
   * 仅当style==Style.BYDAY时有效。
   */
  public Integer getNumberOfDay() {
    return numberOfDay;
  }

  /**
   * 属性：指定第几天。不允许0；允许&lt;0，表示一个月的最后第几天。<br>
   * 仅当style==Style.BYDAY时有效。
   */
  public void setNumberOfDay(Integer numberOfDay) throws BusinessException {
    if (numberOfDay.intValue() == 0)
      throw new BusinessException("属性numberOfDay必须是非0的整数。");
    this.numberOfDay = numberOfDay;
  }

  /**
   * 属性：指定第几个{@link #dayOfWeek}。不允许0；允许&lt;0，表示一个月的倒数第几个。<br>
   * 仅当style==Style.BYWEEK时有效。
   */
  public Integer getNumberOfDOW() {
    return numberOfDOW;
  }

  /**
   * 属性：指定第几个{@link #dayOfWeek}。不允许0；允许&lt;0，表示一个月的倒数第几个。<br>
   * 仅当style==Style.BYWEEK时有效。
   */
  public void setNumberOfDOW(Integer numberOfDOW) throws BusinessException {
    if (numberOfDOW.intValue() == 0)
      throw new BusinessException("属性numberOfDOW必须是非0的整数。");
    this.numberOfDOW = numberOfDOW;
  }

  /** 开始日期 */
  public Date getStartDate() {
    return startDate;
  }

  /** 开始日期 */
  public void setStartDate(Date startDate) {
    this.startDate = DateUtil.truncate(startDate, Calendar.DATE);
  }

  /** 计算模式 */
  public MonthlySchedule.Style getStyle() {
    return style;
  }

  /** 计算模式 */
  public void setStyle(MonthlySchedule.Style style) throws BusinessException {
    if (style == null)
      throw new BusinessException("属性style不允许为null。");
    this.style = style;
  }

  /** 在一天内的时间。仅时间部分有效。 */
  public Date getTime() {
    return time;
  }

  /** 在一天内的时间。仅时间部分有效。 */
  public void setTime(Date time) throws BusinessException {
    if (time == null)
      throw new BusinessException("属性time不允许为null。");
    this.time = DateUtil.getTimePart(time);
  }

}
